#ifndef _LOG_H_
#define _LOG_H_

#include <sstream>
#include <string>
#include <iostream>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <cstdio>

/**
 * Clase utilitaria para escribir mensajes por pantalla.
 */
class Log {

    /**
     * Buffer utilizado para acumular los mensajes hasta que
     * se ordene un flush.
     */
	std::stringstream buffer;

    /**
     * Este string guarda el prefijo que será agregado al comienzo
     * de cada mensaje.
     */
	std::string prefix;

    /**
     * Escribe el contenido del buffer a pantalla en una única
     * operación atómica. Esto quiere decir que si dos procesos
     * quieren escribir mensajes al mismo tiempo, los mismos no
     * serán mezclados.
     */
	void flush() {
		std::string str = prefix + this->buffer.str();
		::write(fileno(stderr), str.c_str(), str.length());
		this->buffer.str("");
	}

    /**
     * Definición del marcador de fin de línea.
     */
	class EndLine {
	};

    /**
     * Definición del marcador de mensaje de error del sistema.
     */
	class ConcatenateError {
	};

public:

    /** Marcador de fin de línea. */
	EndLine endl;

	/** Marcador de mensaje de error del sistema. */
	ConcatenateError err;

    /**
     * Crea un nuevo objeto Log.
     */
	Log() {
		set_pname("");
	}

    /**
     * Asigna el nombre de proceso a mostrar en el prefijo de los mensajes.
     * Los mensajes tienen formato: "[<pname> <pid>] <mensaje>". El pid
     * utilizado es el obtenido la última vez que se llamó a set_pname().
     */
	void set_pname(const std::string& pname) {
		std::stringstream sstream;
		sstream << "[" << pname << " " << getpid() << "] ";
		prefix = sstream.str();
	}

    /**
     * Sobreescribir este operador permite concatenar el último
     * mensaje de error del sistema de la siguiente manera:
     *    log << "error " << log.err << log.endl;
     */
	Log& operator<<(const ConcatenateError& obj) {
		buffer << ": " << strerror(errno);
		return *this;
	}

    /**
     * Sobreescribir este operador permite concatenar el carácter
     * de fin de línea y realizar un flush de la siguiente manera:
     *    log << "acá va el mensaje" << log.endl;
     */
	Log& operator<<(const EndLine& obj) {
		buffer << "\n";
		flush();
		return *this;
	}

    /**
     * Sobreescribir este operador permite escribir cualquier tipo
     * de dato al log.
     */
	template <typename T>
	Log& operator<<(const T& obj) {
		this->buffer << obj;
		return *this;
	}
};

extern Log log;

#endif
